#include "svdpi.h"
#include "common.h"
#include <list>
typedef struct
{
    int rob_idx, prs1, prs2;
    long long src1, src2;
    svBit src1_valid, src2_valid;
    svBit src1_wakeup, src2_wakeup;
} CIQ_Entry;

class CIQ
{
public:
    int size;
    bool inorder;
    std::list<CIQ_Entry> entrys;
    CIQ(int size, bool inorder) : inorder(inorder)
    {
        resize(size);
    }
    void resize(int size)
    {
        this->size = size;
        clear();
    }
    void clear()
    {
        entrys.clear();
    }
    bool full()
    {
        return entrys.size() >= size;
    }
    bool empty()
    {
        return entrys.size() == 0;
    }
    void enq(CIQ_Entry entry)
    {
        Assert(!full(), "CIQ Full when enqueue");
        entrys.push_back(entry);
    }
    bool select_oldest(CIQ_Entry &entry)
    {
        if (inorder)
        {
            if (entrys.empty())
                return false;
            auto &front = entrys.front();
            if ((front.src1_valid | front.src1_wakeup) && (front.src2_valid | front.src2_wakeup))
            {
                entry = front;
                entrys.pop_front();
                return true;
            }
            return false;
        }
        else
        {
            auto it = entrys.begin();
            while (it != entrys.end())
            {
                if ((it->src1_valid | it->src1_wakeup) & (it->src2_valid | it->src2_wakeup))
                {
                    entry = *it;
                    entrys.erase(it);
                    return true;
                }
                it++;
            }
            return false;
        }
    }
    bool deq(CIQ_Entry &entry)
    {
        Assert(!empty(), "CIQ Empty when dequeue");
        return select_oldest(entry);
    }
    void ealry_wake(int prf_idx)
    {
        for (auto &entry : entrys)
        {
            if (!entry.src1_valid && entry.prs1 == prf_idx)
            {
                entry.src1_wakeup = 1;
            }
            if (!entry.src2_valid && entry.prs2 == prf_idx)
            {
                entry.src2_wakeup = 1;
            }
        }
    }
    void wake(int prf_idx, long long prf_data)
    {
        for (auto &entry : entrys)
        {
            if (entry.prs1 == prf_idx)
            {
                entry.src1_valid = 1;
                entry.src1 = prf_data;
            }
            if (entry.prs2 == prf_idx)
            {
                entry.src2_valid = 1;
                entry.src2 = prf_data;
            }
        }
    }
};
static std::vector<CIQ *> ciqs;
static std::vector<std::string> ciq_names;
static int ciq_id = 0;
#ifdef __cplusplus
extern "C"
{
#endif
    int ciq_create(int id, const char *name, int size, svBit inorder)
    {
        if (id == 0)
        {
            ciqs.push_back(new CIQ(size, inorder));
            ciq_names.push_back(std::string(name));
            ciq_id++;
            LOG("CIQ[%d] %s size[%d] order[%s] created", ciq_id, name, size, inorder ? "inorder" : "OoO");
            return ciq_id;
        }
        else
        {
            auto ciq = ciqs[id - 1];
            ciq->clear();
            return id;
        }
    }
    void ciq_deq(int id, int rob_idx, int prs1, int prs2, long long src1, long long src2, svBit src1_valid, svBit src2_valid, svBit src1_wakeup, svBit src2_wakeup)
    {
        // LOG("CIQ deq : %d at %ld", rob_idx, Verilated::time());
        if (id == 0)
            return;
        auto ciq = ciqs[id - 1];

        CIQ_Entry ref_entry = {0};
        bool success = ciq->deq(ref_entry);
        Assert(success, "CIQ Ref not found valid op to issue");
        Assert(ref_entry.rob_idx == rob_idx, "CIQ Ref != dut : rob_idx %d != %d ", ref_entry.rob_idx, rob_idx);
        Assert(ref_entry.prs1 == prs1, "CIQ Ref != dut : prs1 %d != %d ", ref_entry.prs1, prs1);
        Assert(ref_entry.prs2 == prs2, "CIQ Ref != dut : prs2 %d != %d ", ref_entry.prs2, prs2);
        Assert(ref_entry.src1 == src1, "CIQ Ref != dut : src1 %lld != %lld ", ref_entry.src1, src1);
        Assert(ref_entry.src2 == src2, "CIQ Ref != dut : src2 %lld != %lld ", ref_entry.src2, src2);
        Assert(ref_entry.src1_valid == src1_valid, "CIQ Ref != dut : src1_valid %d != %d ", ref_entry.src1_valid, src1_valid);
        Assert(ref_entry.src2_valid == src2_valid, "CIQ Ref != dut : src2_valid %d != %d ", ref_entry.src2_valid, src2_valid);
        Assert(ref_entry.src1_wakeup == src1_wakeup, "CIQ Ref != dut : src1_wakeup %d != %d ", ref_entry.src1_wakeup, src1_wakeup);
        Assert(ref_entry.src2_wakeup == src2_wakeup, "CIQ Ref != dut : src2_wakeup %d != %d ", ref_entry.src2_wakeup, src2_wakeup);
    }
    void ciq_enq(int id, int rob_idx, int prs1, int prs2, long long src1, long long src2, svBit src1_valid, svBit src2_valid, svBit src1_wakeup, svBit src2_wakeup)
    {
        // LOG("CIQ enq : %d Src1[%d]:%d Src2[%d]:%d at %ld", rob_idx, prs1, src1_valid, prs2, src2_valid, Verilated::time());
        if (id == 0)
            return;
        auto ciq = ciqs[id - 1];
        CIQ_Entry entry;
        entry.rob_idx = rob_idx;
        entry.prs1 = prs1;
        entry.prs2 = prs2;
        entry.src1 = src1;
        entry.src2 = src2;
        entry.src1_valid = src1_valid;
        entry.src2_valid = src2_valid;
        entry.src1_wakeup = src1_wakeup;
        entry.src2_wakeup = src2_wakeup;
        ciq->enq(entry);
    }
    void ciq_wake(int id, int prf_idx, long long prf_data)
    {
        // LOG("CIQ wake : %d at %ld", prf_idx, Verilated::time());
        if (id == 0)
            return;
        auto ciq = ciqs[id - 1];
        ciq->wake(prf_idx, prf_data);
    }
    void ciq_early_wake(int id, int prf_idx)
    {
        // LOG("CIQ early wake : %d at %ld", prf_idx, Verilated::time());
        if (id == 0)
            return;
        auto ciq = ciqs[id - 1];
        ciq->ealry_wake(prf_idx);
    }
#ifdef __cplusplus
}
#endif